#!/usr/bin/env python
# -*- coding: utf-8 -*-
####################################################################
# File: tv_qt4.py
# Author: Jean-Marc VARENNE-PAQUET
# Description: This script is a graphical user interface (GUI), 
# that allows users to watch TV, by using the multiposte service 
# from the ADSL and fiber boxes.
# Version: 1.1
# License: GNU GPL v3
####################################################################



# Import
import os, sys, string, datetime, re, urllib2
from PyQt4 import QtCore, QtGui


# Initialization
g_channel_name_list = []
g_channel_urls_hash_table = {}
g_channel_programs_hash_table = {}
g_favorite_channel_name_list = []
g_vlc = 'vlc'
g_configuration_directory = os.environ['HOME'] + "/.config/x_multipost_tv"
g_playlist_url_file_path = g_configuration_directory + "/playlist_url.txt"
g_favorites_file_path = g_configuration_directory + "/favorit_channels.txt"
g_tasks_file = g_configuration_directory + "/crontab_tasks.txt"
g_playlist_url = "http://mafreebox.freebox.fr/freeboxtv/playlist.m3u"











##########################################################
# Classes
##########################################################

# The main window
class Ui_MainWindow(QtGui.QMainWindow):

	# Constructor
	def __init__(self):
		QtGui.QMainWindow.__init__(self)
		self.setWindowTitle("X-Multiposte-TV")
		self.resize(450,750)



		#########################################################################################
		# Menu bar
		#########################################################################################

		# Create menu bar
		menu_bar = QtGui.QMenuBar(self)
		self.setMenuBar(menu_bar)

		# menu File
		menu_file = QtGui.QMenu(menu_bar)
		menu_file.setTitle("Fichier")
		action_open_video_file = QtGui.QAction(self)
		action_open_video_file.setText("Ouvrir un enregistrement video...")
		action_open_video_file.setShortcut(QtGui.QKeySequence("Ctrl+O"))
		action_open_video_file.setIcon(QtGui.QIcon.fromTheme("document-open"))
		QtCore.QObject.connect(action_open_video_file, QtCore.SIGNAL("triggered()"), self.openLocalVideo)
		menu_file.addAction(action_open_video_file)
		menu_file.addSeparator()
		action_exit = QtGui.QAction(self)
		action_exit.setText("Quitter")
		action_exit.setShortcut(QtGui.QKeySequence("Ctrl+Q"))
		action_exit.setIcon(QtGui.QIcon.fromTheme("application-exit"))
		QtCore.QObject.connect(action_exit, QtCore.SIGNAL("triggered()"), self.close)
		menu_file.addAction(action_exit)
		menu_bar.addAction(menu_file.menuAction())

		# menu TV guide
		menu_tv_guide = QtGui.QMenu(menu_bar)
		menu_tv_guide.setTitle("GuideTV")
		action_open_tv_guide = QtGui.QAction(self)
		action_open_tv_guide.setText("Ouvrir le guide TV")
		action_open_tv_guide.setIcon(QtGui.QIcon.fromTheme("document-open-remote"))
		QtCore.QObject.connect(action_open_tv_guide, QtCore.SIGNAL("triggered()"), lambda: QtGui.QDesktopServices.openUrl(QtCore.QUrl("http://streamplayer.free.fr/tv/guidetv.php")))
		menu_tv_guide.addAction(action_open_tv_guide)
		menu_bar.addAction(menu_tv_guide.menuAction())

		# menu Configuration
		menu_configuration = QtGui.QMenu(menu_bar)
		menu_configuration.setTitle("Configuration")
		action_set_playlist = QtGui.QAction(self)
		action_set_playlist.setText("Definir la playlist a utiliser")
		action_set_playlist.setIcon(QtGui.QIcon.fromTheme("configure"))
		QtCore.QObject.connect(action_set_playlist, QtCore.SIGNAL("triggered()"), self.displayConfigurationWindow)
		menu_configuration.addAction(action_set_playlist)
		menu_bar.addAction(menu_configuration.menuAction())

		# menu Help
		menu_help = QtGui.QMenu(menu_bar)
		menu_help.setTitle("Aide")
		action_about_qt = QtGui.QAction(self)
		action_about_qt.setText("A propos de QT")
		action_about_qt.setIcon(QtGui.QIcon.fromTheme("help-about"))
		QtCore.QObject.connect(action_about_qt, QtCore.SIGNAL("triggered()"), self.displayAboutQTWindow)
		menu_help.addAction(action_about_qt)
		action_about_app = QtGui.QAction(self)
		action_about_app.setText("A propos de cette application")
		action_about_app.setIcon(QtGui.QIcon.fromTheme("emoticon"))
		QtCore.QObject.connect(action_about_app, QtCore.SIGNAL("triggered()"), self.displayAboutAppWindow)
		menu_help.addAction(action_about_app)
		menu_bar.addAction(menu_help.menuAction())




		#########################################################################################
		# Central Widget
		#########################################################################################

		self.central_widget = QtGui.QWidget()
		central_layout = QtGui.QVBoxLayout(self.central_widget)
		page1_widget = QtGui.QWidget()
		page1_layout = QtGui.QVBoxLayout(page1_widget)
		page2_widget = QtGui.QWidget()
		page2_layout = QtGui.QVBoxLayout(page2_widget)
		self.tab_widget = QtGui.QTabWidget()
		self.tab_widget.insertTab(0,page1_widget,"Toutes")
		self.tab_widget.insertTab(1,page2_widget,"Favorites")
		central_layout.addWidget(self.tab_widget)
		self.setCentralWidget(self.central_widget)

		# Search field
		self.line_edit = QtGui.QLineEdit()
		self.line_edit.setStyleSheet("padding:1 20px;background:url(\"/usr/share/icons/oxygen/16x16/actions/zoom-original.png\") no-repeat left center; border-radius: 10px;border-width: 2px;border-style: outset;")
		QtCore.QObject.connect(self.line_edit,QtCore.SIGNAL("textChanged(const QString &)"), self.updateModel)
		page1_layout.addWidget(self.line_edit)

		# Main channels list
		self.list_view_page1 = QtGui.QListView()
		QtCore.QObject.connect(self.list_view_page1,QtCore.SIGNAL("doubleClicked(const QModelIndex &)"), self.watchTV)
		self.list_view_page1.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
		QtCore.QObject.connect(self.list_view_page1, QtCore.SIGNAL("customContextMenuRequested (const QPoint&)"), self.displayContextMenuAll)
		page1_layout.addWidget(self.list_view_page1)
		self.model = QtGui.QStringListModel()
		self.list_view_page1.setModel(self.model)

		# Favorite channels list
		self.list_view_page2 = QtGui.QListView()
		QtCore.QObject.connect(self.list_view_page2,QtCore.SIGNAL("doubleClicked(const QModelIndex &)"), self.watchTV)
		self.list_view_page2.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
		QtCore.QObject.connect(self.list_view_page2, QtCore.SIGNAL("customContextMenuRequested (const QPoint&)"), self.displayContextMenuFavorites)
		page2_layout.addWidget(self.list_view_page2)
		self.model2 = QtGui.QStringListModel()
		self.list_view_page2.setModel(self.model2)

		# Focus
		self.line_edit.setFocus()



		#########################################################################################
		# Create a directory for the configuration files
		#########################################################################################

		if not os.path.exists(g_configuration_directory):
			try:
				os.makedirs(g_configuration_directory)
			except OSError as msg:
				print(msg)



		#########################################################################################
		# Load data
		#########################################################################################

		self.readConfigurationFile()
		self.loadPlaylist()
		self.reloadFavoritesFile()

	# Update the channels list displayed. It takes into account the filter from the search field. 
	def updateModel(self, string):
		string_list = []
		for channel_name in g_channel_name_list:
			minus_channel_name = channel_name.lower()
			minus_string = string.toUtf8().data().decode('utf-8').lower()
			if minus_channel_name.find(minus_string) >= 0:
				string_list.append(channel_name)
		self.model.setStringList(string_list)

	# Run VLC to display a channel.
	def watchTV(self, model_index):
		channel_name =  model_index.data().toString().toUtf8().data().decode('utf-8')
		channel_url = g_channel_urls_hash_table[channel_name]
		command = g_vlc + " --video-filter=deinterlace --deinterlace-mode=yadif2x \"" + channel_url + "\" &"
		print(command)
		os.system(command)

	# Run VLC to watch and record a channel at the same time.
	def watchAndRecordTV(self, model_index):
		channel_name = model_index.data().toString().toUtf8().data().decode('utf-8')
		channel_url = g_channel_urls_hash_table[channel_name]
		output_file_path = os.environ['HOME'] + os.sep + channel_name.encode('utf-8').replace(" ","") + "__" + str(datetime.datetime.today()).replace(":","-").replace(" ","__") + ".ts"
		command = g_vlc + " \"" + channel_url + "\" --sout=\"#duplicate{dst=std{access=file,mux=ts,dst=" + output_file_path + "},dst=display}\" &"
		print(command)
		os.system(command)

	# Display a window which allows to plan a record from a date to an other.
	def planRecord(self, model_index):
		channel_name = model_index.data().toString().toUtf8().data().decode('utf-8')
		channel_url = g_channel_urls_hash_table[channel_name]
		self.record_programming_window = Ui_RecordPlanningWindow(channel_name, channel_url)
		self.record_programming_window.show()

	# Open a web site on the corresponding channel TV guide.
	def openWebTVGuide(self, model_index):
		channel_name = model_index.data().toString().toUtf8().data().decode('utf-8')
		channel_number = ""
		myPattern = re.compile('^(\d+)')
		m = re.search(myPattern, channel_name)
		try:
			channel_number = m.groups(1)[0]
		except:
			pass
		QtGui.QDesktopServices.openUrl(QtCore.QUrl("http://streamplayer.free.fr/tv/guidetv.php?chaines=freebox-tv#c" + channel_number))

	# Add a channel in the favorite channels list.
	def addToFavorites(self, model_index):
		global g_favorite_channel_name_list
		channel_name = model_index.data().toString().toUtf8().data().decode('utf-8')
		g_favorite_channel_name_list.append(channel_name)
		self.model2.setStringList(g_favorite_channel_name_list)
		self.updateFavoritesFile()

	# Remove a channel from the favorite channels list.
	def removeFromFavorites(self, model_index):
		global g_favorite_channel_name_list
		channel_name = model_index.data().toString().toUtf8().data().decode('utf-8')
		g_favorite_channel_name_list.remove(channel_name)
		self.model2.setStringList(g_favorite_channel_name_list)
		self.updateFavoritesFile()

	# Update the favorite channels list file.
	def updateFavoritesFile(self):
		try:
			favorite_channels_file = open(g_favorites_file_path,'w')
			for channel_name in g_favorite_channel_name_list:
				favorite_channels_file.write(channel_name.encode('utf-8') + "\n")
			favorite_channels_file.close()
		except IOError as msg:
			print("ERROR: Cannot write in " + g_favorites_file_path)
			print(msg)
			sys.exit(1)

	# Display a context menu. This one is called by a right click on a channel name in the main channels list.
	def displayContextMenuAll(self, point):
		model_index = self.list_view_page1.indexAt(point)
		channel_name = model_index.data().toString().toUtf8().data().decode('utf-8')

		menu = QtGui.QMenu("Menu", self)

		action_channel_name = QtGui.QAction(self)
		action_channel_name.setText(channel_name)
		menu.addAction(action_channel_name)

		menu.addSeparator()

		action_watch = QtGui.QAction(self)
		action_watch.setText("Regarder")
		QtCore.QObject.connect(action_watch, QtCore.SIGNAL("triggered()"), lambda: self.watchTV(model_index))
		menu.addAction(action_watch)

		action_watch_and_record = QtGui.QAction(self)
		action_watch_and_record.setText("Enregistrer et regarder")
		QtCore.QObject.connect(action_watch_and_record, QtCore.SIGNAL("triggered()"), lambda: self.watchAndRecordTV(model_index))
		menu.addAction(action_watch_and_record)

		action_plan_record = QtGui.QAction(self)
		action_plan_record.setText("Planifier un enregistrement")
		QtCore.QObject.connect(action_plan_record, QtCore.SIGNAL("triggered()"), lambda: self.planRecord(model_index))
		menu.addAction(action_plan_record)

		action_open_tv_guide = QtGui.QAction(self)
		action_open_tv_guide.setText("Ouvrir le guide TV sur cette chaine")
		QtCore.QObject.connect(action_open_tv_guide, QtCore.SIGNAL("triggered()"), lambda: self.openWebTVGuide(model_index))
		menu.addAction(action_open_tv_guide)

		action_add_to_favorites = QtGui.QAction(self)
		action_add_to_favorites.setText("Ajouter aux favoris")
		QtCore.QObject.connect(action_add_to_favorites, QtCore.SIGNAL("triggered()"), lambda: self.addToFavorites(model_index))
		menu.addAction(action_add_to_favorites)

		menu.exec_(self.mapToGlobal(point)) 

	# Display a context menu. This one is called by a right click on a channel name in the favorite channels list.
	def displayContextMenuFavorites(self, point):
		model_index = self.list_view_page2.indexAt(point)
		channel_name = model_index.data().toString().toUtf8().data().decode('utf-8')

		menu = QtGui.QMenu("Menu", self)

		action_channel_name = QtGui.QAction(self)
		action_channel_name.setText(channel_name)
		menu.addAction(action_channel_name)

		menu.addSeparator()

		action_watch = QtGui.QAction(self)
		action_watch.setText("Regarder")
		QtCore.QObject.connect(action_watch, QtCore.SIGNAL("triggered()"), lambda: self.watchTV(model_index))
		menu.addAction(action_watch)

		action_watch_and_record = QtGui.QAction(self)
		action_watch_and_record.setText("Enregistrer et regarder")
		QtCore.QObject.connect(action_watch_and_record, QtCore.SIGNAL("triggered()"), lambda: self.watchAndRecordTV(model_index))
		menu.addAction(action_watch_and_record)

		action_plan_record = QtGui.QAction(self)
		action_plan_record.setText("Planifier un enregistrement")
		QtCore.QObject.connect(action_plan_record, QtCore.SIGNAL("triggered()"), lambda: self.planRecord(model_index))
		menu.addAction(action_plan_record)

		action_open_tv_guide = QtGui.QAction(self)
		action_open_tv_guide.setText("Ouvrir le guide TV sur cette chaine")
		QtCore.QObject.connect(action_open_tv_guide, QtCore.SIGNAL("triggered()"), lambda: self.openWebTVGuide(model_index))
		menu.addAction(action_open_tv_guide)

		action_remove_from_favorites = QtGui.QAction(self)
		action_remove_from_favorites.setText("Retirer des favoris")
		QtCore.QObject.connect(action_remove_from_favorites, QtCore.SIGNAL("triggered()"), lambda: self.removeFromFavorites(model_index))
		menu.addAction(action_remove_from_favorites)

		menu.exec_(self.mapToGlobal(point)) 

	# Read configuration file
	def readConfigurationFile(self):
		global g_playlist_url
		try:
			configuration_file = open(g_playlist_url_file_path,'r')
			g_playlist_url = configuration_file.readline()
			configuration_file.close()
		except:
			pass

	# Load the remote Freebox playlist file
	def loadPlaylist(self):
		global g_channel_name_list
		global g_channel_urls_hash_table
		g_channel_name_list = []
		g_channel_urls_hash_table = {}

		playlist_file_lines = []
		try:
			playlist_file = urllib2.urlopen(g_playlist_url)
			playlist_file_lines = playlist_file.readlines()
			playlist_file.close()
		except urllib2.URLError as msg:
			print("WARNING: Cannot open playlist " + g_playlist_url)
			print(msg)
			QtGui.QMessageBox.warning(self, 'Attention','La playlist suivante n\'est pas accessible : \n' + g_playlist_url)

		channel_name = ""
		for line in playlist_file_lines:
			if line.startswith('#EXTINF:'):
				splitted_line = string.split(line,",")
				channel_name = splitted_line[1].rstrip()
				channel_name = channel_name.decode('utf-8')
				g_channel_name_list.append(channel_name)
			if line.startswith('rtsp:'):
				channel_url = line.rstrip()
				g_channel_urls_hash_table[channel_name] = channel_url

		self.updateModel(self.line_edit.text())

	# Reload the favorites file
	def reloadFavoritesFile(self):
		global g_favorite_channel_name_list
		if os.path.exists(g_favorites_file_path):
			# Get favorite channels list from the file.
			g_favorite_channel_name_list = []
			file_lines = []
			try:
				favorite_channels_file = open(g_favorites_file_path, 'r')
				file_lines = favorite_channels_file.readlines()
				favorite_channels_file.close()
			except IOError as msg:
				print("ERROR: Cannot read " + g_favorites_file_path)
				print(msg)
				sys.exit(1)

			# Update the favorite channels list and display it.
			for line in file_lines:
				line = line.rstrip()
				channel_name = line.decode('utf-8')
				g_favorite_channel_name_list.append(channel_name)
			self.model2.setStringList(g_favorite_channel_name_list)

	# Open a window to select a video record from the hard disk.
	def openLocalVideo(self):
		local_video_file_path = QtGui.QFileDialog().getOpenFileName(self, 'Charger une video', os.environ['HOME'], 'file type 1 (*.ts);;All files (*)', '')
		local_video_file_path = local_video_file_path.toUtf8().data()
		if local_video_file_path != "":
			command = g_vlc + " --video-filter=deinterlace --deinterlace-mode=yadif2x \"" + local_video_file_path + "\" &"
			print(command)
			os.system(command)

	# Open a window which displays information about the QT version.
	def displayAboutQTWindow(self):
		self.about_window = QtGui.QMessageBox()
		self.about_window.aboutQt(self, "A propos de QT")

	# Open a window which displays information about the script.
	def displayAboutAppWindow(self):
		self.about_window = QtGui.QMessageBox()
		self.about_window.about(self, "A propos", self.windowTitle() + "\nRealisation de Jean-Marc VARENNE-PAQUET\nVersion 1.0\nSous licence GNU GPL v3")

	# Display a window to set the playlist to use.
	def displayConfigurationWindow(self):
		self.configuration_window = Ui_ConfigurationWindow()
		self.configuration_window.exec_()
		self.loadPlaylist()







# The window to set the playlist to use.
class Ui_ConfigurationWindow(QtGui.QDialog):

	# Constructor
	def __init__(self):
		QtGui.QDialog.__init__(self)

		self.setWindowTitle("Definir une playlist a utiliser")
		self.resize(450,100)

		# Build the window
		vertical_layout = QtGui.QVBoxLayout(self)
		self.line_edit = QtGui.QLineEdit()
		self.line_edit.setText(g_playlist_url)
		vertical_layout.addWidget(self.line_edit)

		# Ok, cancel buttons
		self.button_box = QtGui.QDialogButtonBox()
		self.button_box.setOrientation(QtCore.Qt.Horizontal)
		self.button_box.setStandardButtons(QtGui.QDialogButtonBox.Ok|QtGui.QDialogButtonBox.Cancel)
		QtCore.QObject.connect(self.button_box, QtCore.SIGNAL("accepted()"), self.updateConfigurationFile)
		QtCore.QObject.connect(self.button_box, QtCore.SIGNAL("rejected()"), self.close)
		vertical_layout.addWidget(self.button_box)

		QtCore.QMetaObject.connectSlotsByName(self)

	# Update the configuration file with the new playlist
	def updateConfigurationFile(self):
		global g_playlist_url
		g_playlist_url = str(self.line_edit.text())
		try:
			configuration_file = open(g_playlist_url_file_path,'w')
			configuration_file.write(g_playlist_url)
			configuration_file.close()
		except IOError as msg:
			print("WARNING: Cannot write configuration file " + g_playlist_url_file_path)
			print(msg)
		self.close()








# The window to plan video records.
class Ui_RecordPlanningWindow(QtGui.QDialog):

	# Constructor
	def __init__(self, channel_name, channel_url):
		QtGui.QDialog.__init__(self)

		self.setWindowTitle("Enregistrement")
		self.channel_name = channel_name
		self.channel_url = channel_url

		# Build the window.
		layout = QtGui.QGridLayout(self)

		label_channel_name = QtGui.QLabel()
		label_channel_name.setText("Enregistrement de " + self.channel_name)
		layout.addWidget(label_channel_name, 0, 0, 1, 0)

		# Start date
		label_start = QtGui.QLabel()
		label_start.setText("Date de debut")
		layout.addWidget(label_start, 1, 0)
		self.date_time_edit_start = QtGui.QDateTimeEdit()
		self.date_time_edit_start.setDateTime(QtCore.QDateTime.currentDateTime())
		layout.addWidget(self.date_time_edit_start, 1, 1)

		# End date
		label_end = QtGui.QLabel()
		label_end.setText("Date de fin")
		layout.addWidget(label_end, 2, 0)
		self.date_time_edit_end = QtGui.QDateTimeEdit()
		self.date_time_edit_end.setDateTime(QtCore.QDateTime.currentDateTime())
		layout.addWidget(self.date_time_edit_end, 2, 1)

		# Ok, cancel buttons
		self.button_box = QtGui.QDialogButtonBox()
		self.button_box.setOrientation(QtCore.Qt.Horizontal)
		self.button_box.setStandardButtons(QtGui.QDialogButtonBox.Ok|QtGui.QDialogButtonBox.Cancel)
		QtCore.QObject.connect(self.button_box, QtCore.SIGNAL("accepted()"), self.planRecord)
		QtCore.QObject.connect(self.button_box, QtCore.SIGNAL("rejected()"), self.close)
		layout.addWidget(self.button_box, 3, 0)

		QtCore.QMetaObject.connectSlotsByName(self)

	# Use the contab to plan a record between the two dates.
	def planRecord(self):
		output_file_path = os.environ['HOME'] + os.sep + self.channel_name.encode('utf-8').replace(" ","") + "__" + str(self.date_time_edit_start.dateTime().toString("yyyy-MM-d__hh-mm")) + ".ts"
		print(output_file_path)
		print("Start date: " + str(self.date_time_edit_start.dateTime().toString("mm hh d MM")))
		print("End date: " + str(self.date_time_edit_end.dateTime().toString("mm hh d MM")))

		# Write a temporary file with the crontab tasks.
		try:
			tasks_file = open(g_tasks_file,'w')
			tasks_file.write(str(self.date_time_edit_start.dateTime().toString("mm hh d MM")) + " * " + g_vlc + " \"" + self.channel_url + "\" --intf dummy --sout file/ts:" + output_file_path + "\n")
			tasks_file.write(str(self.date_time_edit_end.dateTime().toString("mm hh d MM")) + " * killall vlc && crontab -r\n")
			tasks_file.close()
		except IOError as msg:
			print("ERROR: Cannot write in " + g_tasks_file)
			print(msg)
			sys.exit(1)

		# Load the temporary file in crontab.
		command = "crontab " + g_tasks_file
		os.system(command)

		self.close()











##########################################################
# Main
##########################################################

if __name__ == "__main__":

	# Display the main window
	app = QtGui.QApplication(sys.argv)
	main_window = Ui_MainWindow()
	main_window.show()
	sys.exit(app.exec_())




